//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using Flare.Events;
using Flare.Geom;

namespace Flare.Display
{
    /// <summary>
    /// The Stage class represents the area where the SWF content is drawn. Many of the
    /// inherited properties and methods are inapplicable to instances of the Stage class
    /// (e.g., alpha, blendMode, mask, etc.).
    /// </summary>
    public class Stage : DisplayObjectContainer
    {
        // TODO bwmott 2012/11/07: Throw exception on illegal operations

        /// <summary>
        /// Specifies the alignment of the stage within the player using a value
        /// from the StageAlign class.
        /// </summary>
        public string align { get; set; }

        /// <summary>
        /// Stage background color which defaults to the SWF background color.
        /// </summary>
        public uint color { get; set; }

        /// <summary>
        /// The interactive object with keyboard focus or null if focus is not set.
        /// </summary>
        public InteractiveObject focus
        {
            get
            {
                return this.m_focus;
            }
            set
            {
                this.SetFocus(value);
            }
        }
        private InteractiveObject m_focus = null;

        /// <summary>
        /// Gets or sets the frame rate.
        /// </summary>
        public float frameRate { get; set; }

        /// <summary>
        /// Native window containing the stage.
        /// </summary>
        public NativeWindow nativeWindow { get; private set; }

        /// <summary>
        /// Specifies the scale mode to use for SWF content using a value from the
        /// StageScaleMode class.
        /// </summary>
        public string scaleMode { get; set; }

        /// <summary>
        /// Current height of the stage in pixels.
        /// </summary>
        public float stageHeight
        {
            // TODO bwmott 2013-09-08: When NO_SCALE mode is supported this should return
            // the size of the available stage area (see AS3 docs).
            get { return this.originalStageHeight; }
        }

        /// <summary>
        /// Current width of the stage in pixels.
        /// </summary>
        public float stageWidth
        {
            // TODO bwmott 2013-09-08: When NO_SCALE mode is supported this should return
            // the size of the available stage area (see AS3 docs).
            get { return this.originalStageWidth; }
        }

        internal float originalStageHeight { get; private set; }
        internal float originalStageWidth { get; private set; }

        internal Stage(NativeWindow nativeWindow,
            float originalStageWidth, float originalStageHeight)
        {
            this.nativeWindow = nativeWindow;
            this.originalStageWidth = originalStageWidth;
            this.originalStageHeight = originalStageHeight;

            this.align = StageAlign.CENTER;
            this.scaleMode = StageScaleMode.EXACT_FIT;
            this.color = 0x000000;
            this.frameRate = 24.0f;

            // Add a keyboard event handler so that the focus can be updated as necessary
            this.AddEventListener(KeyboardEvent.KEY_DOWN, OnKeyPress);

            // Add a mouse event handler so that the focus can be updated as necessary
            this.AddEventListener(MouseEvent.MOUSE_DOWN, OnMouseClick);
        }
        
        internal override bool HitTest(float x, float y, bool shapeFlag, bool evaluateChildren)
        {
            return ((x >= 0.0f) && (x < this.stageWidth) && (y >= 0.0f) && (y < this.stageHeight));
        }

        /// <summary>
        /// Return a clone of the Stage with a deep copy of any mutable members.
        /// </summary>
        public override object Clone()
        {
            Stage clone = (Stage)base.Clone();

            // TODO bwmott 2013/01/30: Decide how the members should be copied for a Stage
            // or perhaps have this method throw an exception as not supported

            return clone;
        }

        // Handle mouse click for focusing on interactive objects
        private void OnMouseClick(Flare.Events.Event evt)
        {   
            var mouseEvent = evt as MouseEvent;

            if (mouseEvent.target is InteractiveObject)
            {
                var obj = mouseEvent.target as InteractiveObject;
                if (this.focus != obj)
                {
                    this.focus = obj;
                }
            }
        }

        // Handle key press to tab between interactive objects
        private void OnKeyPress(Flare.Events.Event evt)
        {
            var keyEvent = evt as KeyboardEvent;

            // If the tab key is pressed, look at updating the object with focus
            if (keyEvent.keyCode == Flare.UI.Keyboard.KeyCode.TAB)
            {
                List<InteractiveObject> objs = new List<InteractiveObject>();
                this.FindTabEnabledInteractiveObjects(this, objs);

                if (objs.Count > 0)
                {
                    // See if any of the tab enabled objects have an assigned index
                    bool somethingIsIndexed = false;
                    for (int i = 0; i < objs.Count; ++i)
                    {
                        if (objs[i].tabIndex != -1)
                        {
                            somethingIsIndexed = true;
                            break;
                        }
                    }

                    // Figure out which object to move the focus to
                    InteractiveObject newFocus = null;

                    if (somethingIsIndexed)
                    {
                        objs.Sort(delegate(InteractiveObject o1, InteractiveObject o2)
                            {
                                return o1.tabIndex - o2.tabIndex;
                            });

                        int index = (this.focus != null) ? this.focus.tabIndex : -1;
                        for (int i = 0; i < objs.Count; ++i)
                        {
                            if (objs[i].tabIndex > index)
                            {
                                newFocus = objs[i];
                                break;
                            }
                        }
                    }
                    else
                    {
                        for (int i = 0; i < objs.Count; ++i)
                        {
                            if (objs[i] == this.focus)
                            {
                                newFocus = objs[(i + 1) % objs.Count];
                                break;
                            }
                        }
                    }

                    this.focus = (newFocus != null) ? newFocus : objs[0];
                }
            }
        }

        private void FindTabEnabledInteractiveObjects(DisplayObject obj,
            List<InteractiveObject> list)
        {
            if (obj is InteractiveObject)
            {
                if ((obj.visible) && (!obj.isMask) && (obj as InteractiveObject).tabEnabled)
                {
                    list.Add(obj as InteractiveObject);
                }
            }
            
            if (obj is DisplayObjectContainer)
            {
                DisplayObjectContainer container = (DisplayObjectContainer)obj;
                for (int i = 0; i < container.numChildren; ++i)
                {
                    FindTabEnabledInteractiveObjects(container.GetChildAt(i), list);
                }
            }
        }
        
        private void SetFocus(InteractiveObject value)
        {
            if (this.m_focus != value)
            {
                if (this.m_focus != null)
                {
                    var evt = new FocusEvent(FocusEvent.FOCUS_OUT, true, false, value);
                    this.m_focus.DispatchEvent(evt);
                }

                if (value != null)
                {
                    var evt = new FocusEvent(FocusEvent.FOCUS_IN, true, false, this.m_focus);
                    value.DispatchEvent(evt);
                }

                this.m_focus = value;
            }
        }
    }
}
